package edu.uaskl.jqf.model;

import java.util.concurrent.ThreadLocalRandom;

import edu.uaskl.jqf.model.matrix.MatrixComplex;

/**
 * Immutable.<br>
 * Represents alpha * |0> + beta * |1> with alpha, beta element C and abs(alpha)^2 + abs(beta^2) = 1 <br>
 * |0> represents (1,0)^t and |1> represents (0,1)^t <br>
 * Or, we can call it a 2x1 matrix (alpha, beta)^t<br>
 * 
 * @author tbach
 */
public class Qubit extends MatrixComplex {
    private static final double epsilon = 10E-6;

    /** Represents the bit 0 in the qubit world, i.e. |0> = (1,0)^t */
    public static final Qubit q0 = new Qubit(Complex.get10(), Complex.get00());

    /** Represents the bit 1 in the qubit world, i.e. |1> = (0,1)^t */
    public static final Qubit q1 = new Qubit(Complex.get00(), Complex.get10());

    public Qubit(final double alpha, final double beta) {
        this(new Complex(alpha), new Complex(beta));
    }

    public Qubit(final Complex alpha, final Complex beta) {
        super(new Complex[][] { { alpha }, { beta } });
        check();
    }

    public Complex getAlpha() {
        return get(0, 0);
    }

    public double getProbabilityFor0() {
        return getAlpha().getAbsSquare();
    }

    public Complex getBeta() {
        return get(1, 0);
    }

    public double getProbabilityFor1() {
        return getBeta().getAbsSquare();
    }

    @Override
    public Qubit multiply(final MatrixComplex other) {
        final MatrixComplex newMatrix = super.multiply(other);
        final Complex newAlpha = newMatrix.get(0, 0);
        final Complex newBeta = newMatrix.get(1, 1);
        return new Qubit(newAlpha, newBeta);
    }

    private void check() {
        final double probability = getProbabilityFor0() + getProbabilityFor1();
        if (!(Math.abs(probability - 1) < epsilon))
            throw new IllegalStateException(String.format(
                    "Probability is not normalized, abs(alpha)^2 + abs(beta)^2 = abs(%s)^2 + abs(%s)^2 = %f + %f = %f != 1", getAlpha(), getBeta(),
                    getProbabilityFor0(), getProbabilityFor1(), probability));
    }

    /**
     * Measures the qubit according to the current alpha and beta. The result is either 0 or 1<br>
     * The result is randomized, so even if we have 90% for 0, it could be 1 in one of then cases.<br>
     * To get the exact representation, use {@link #toString()} or {@link #toStringProbability()}<br>
     * In "reality", the Measurement will collapse the current state. In this implementation, subsequent calls can have different results.
     */
    public int measure() {
        // plan: get probability of alpha, we do need to calculate beta because of p(alpha) + p(beta) = 1
        // Therefore, p(beta) = 1 - p(alpha)
        // If we have random value between 0 and 1, then either it is in the range of [0, p(alpha)[ or [p(alpha), 1[ = [1-p(beta), 1[
        final double probabilityFor0 = getProbabilityFor0();
        final double random = ThreadLocalRandom.current().nextDouble();
        return (random < probabilityFor0) ? 0 : 1;
    }

    @Override
    public String toString() {
        return getAlpha() + " * |0> " + getBeta() + " * |1>";
    }

    public String toStringAsMatrix() {
        return super.toString();
    }

    public String toStringProbability() {
        return getAlpha().getAbsSquare() + " * |0> + " + getBeta().getAbsSquare() + " * |1>";
    }
}
